use anyhow::Error;
use codec::{CopyConfigData, DeleteConfigData, EditConfigData, GetConfigData, GetData};
use log::error;

use std::sync::Arc;
use tokio::sync::RwLockReadGuard;
use yang2::{
    context::Context,
    data::{Data, DataNodeRef, DataTree},
    schema::SchemaNodeKind,
};

pub mod client;
pub mod codec;

pub const DEFAULT_SSH_PORT: u16 = 8300;
pub const DEFAULT_YANG_BASE: &str = ".";
pub const NC_TRUNK_FRAG_SIZE: usize = 40960;

#[derive(Clone, Debug)]
pub struct Hello {
    capabilities: Capabilities,
    session_id: u32,
}

#[derive(Clone, Debug)]
pub struct Capabilities;

#[derive(Clone, Debug)]
pub struct Capability {
    pub namespace: String,
    pub name: String,
}

impl XmlEncodeAble for Hello {
    fn encode_xml(&self) -> String {
        return format!(
            r#"
<hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
    {}
    <session-id>{}</session-id>
</hello>
        "#,
            self.capabilities.encode_xml(),
            self.session_id
        );
    }
}

impl XmlEncodeAble for Capabilities {
    fn encode_xml(&self) -> String {
        r#"
<capabilities>
    <capability>urn:ietf:params:netconf:base:1.1</capability>
    <capability>urn:ietf:params:netconf:capability:startup:1.0</capability>
    <capability>urn:ietf:params:netconf:capability:writable-running:1.0</capability>
    <capability>urn:ietf:params:netconf:capability:notification:1.0</capability>
    <capability>urn:ietf:params:netconf:capability:interleave:1.0</capability>
    <capability>urn:ietf:params:netconf:capability:xpath:1.0</capability>
</capabilities>
        "#
        .to_string()
    }
}

impl XmlEncodeAble for Capability {
    fn encode_xml(&self) -> String {
        return format!(r#"<capability>{}</capability>"#, &self.namespace);
    }
}

impl Capability {
    pub fn new(namespace: &str, name: &str) -> Self {
        Self {
            namespace: namespace.to_string(),
            name: name.to_string(),
        }
    }
}

#[derive(Debug, Clone)]
pub enum NcMsg {
    Rpc(String, NcopMsg),
    Hello(Hello),
    RpcReply(String, NcopMsg),
    /// (time, data)
    Notifycation(String, String),
    /// message_id, reason
    InvelidMsg(String, String),
}

impl NcMsg {
    pub fn to_readable_string(&self) -> String {
        match self {
            NcMsg::Rpc(msg_id, msg) => format!("[{}] {:?}", msg_id, msg),
            NcMsg::InvelidMsg(_msg_id, _err) => "invalid msg".to_string(),
            NcMsg::RpcReply(msg_id, msg) => match msg {
                NcopMsg::Data(dt) => dt.to_string(),
                NcopMsg::Ok => "ok".to_string(),
                NcopMsg::RpcError(et, etag, es, ei) => format!(
                    "[{} {} {}] {}",
                    es.encode_string(),
                    et.encode_string(),
                    etag.encode_string(),
                    ei.content
                ),
                _ => format!("[{}] {:?}", msg_id, msg),
            },
            NcMsg::Hello(_) => "hello".to_string(),
            NcMsg::Notifycation(tm, evt) => format!("[notify] [{}] {}", tm, evt),
        }
    }
}

#[derive(Debug, Clone)]
pub enum NcopMsg {
    /// get
    Get(GetData),
    /// get-config
    GetConfig(GetConfigData),
    /// copy-config
    CopyConfig(CopyConfigData),
    /// edit-config
    EditConfig(EditConfigData),
    /// delete-config
    DeleteConfig(DeleteConfigData),
    KillSession,
    CloseSession,
    Ok,
    RpcError(ErrorType, ErrorTag, ErrorSeverity, ErrorInfo),
    Data(String),
    /// RpcCall(ns, name, rpc_tree)
    RpcCall(String, String, Arc<DataTree>),
    CreateSubscription,
}

pub trait XmlEncodeAble {
    fn encode_xml(&self) -> String;
}

#[derive(Debug, Clone, Default)]
pub enum ErrorType {
    Transport,
    Rpc,
    Protocol,
    #[default]
    Application,
}

impl ErrorType {
    pub fn from_str(str: &str) -> Self {
        match str {
            "application" => ErrorType::Application,
            "rpc" => ErrorType::Rpc,
            "transport" => ErrorType::Transport,
            _ => ErrorType::Protocol,
        }
    }
}

impl EncodeString for ErrorType {
    fn encode_string(&self) -> String {
        match self {
            ErrorType::Transport => "transport".to_string(),
            ErrorType::Rpc => "rpc".to_string(),
            ErrorType::Protocol => "protocol".to_string(),
            ErrorType::Application => "application".to_string(),
        }
    }
}

#[derive(Debug, Clone, Default)]
pub enum ErrorTag {
    InUse,
    InvalidValue,
    TooBig,
    MissingAttribute,
    BadAttribute,
    UnknownAttribute,
    MissingElement,
    #[default]
    BadElement,
    UnknownElement,
    UnknownNamespace,
    AccessDenied,
    LockDenied,
    ResourceDenied,
    RollbackFailed,
    DataExists,
    DataMissing,
    OperationNotSupported,
    OperationFailed,
    PartialOperation,
    MalformedMessage,
}

impl ErrorTag {
    pub fn from_str(str: &str) -> Self {
        match str {
            "in-use" => ErrorTag::InUse,
            "invalid-value" => ErrorTag::InvalidValue,
            "too-big" => ErrorTag::TooBig,
            "missing-attribute" => ErrorTag::MissingAttribute,
            "bad-attribute" => ErrorTag::BadAttribute,
            "unknown-attribute" => ErrorTag::UnknownAttribute,
            "missing-element" => ErrorTag::MissingElement,
            "bad-element" => ErrorTag::BadElement,
            "unknown-element" => ErrorTag::UnknownElement,
            "unknown-namespace" => ErrorTag::UnknownNamespace,
            "access-denied" => ErrorTag::AccessDenied,
            "lock-denied" => ErrorTag::LockDenied,
            "resource-denied" => ErrorTag::ResourceDenied,
            "rollback-failed" => ErrorTag::RollbackFailed,
            "data-exists" => ErrorTag::DataExists,
            "data-missing" => ErrorTag::DataMissing,
            "operation-not-supported" => ErrorTag::OperationNotSupported,
            "operation-failed" => ErrorTag::OperationFailed,
            "partial-pperation" => ErrorTag::PartialOperation,
            "malformed-message" => ErrorTag::MalformedMessage,
            _ => ErrorTag::BadElement,
        }
    }
}

impl EncodeString for ErrorTag {
    fn encode_string(&self) -> String {
        match self {
            ErrorTag::InUse => "in-use".to_string(),
            ErrorTag::InvalidValue => "invalid-value".to_string(),
            ErrorTag::TooBig => "too-big".to_string(),
            ErrorTag::MissingAttribute => "missing-attribute".to_string(),
            ErrorTag::BadAttribute => "bad-attribute".to_string(),
            ErrorTag::UnknownAttribute => "unknown-attribute".to_string(),
            ErrorTag::MissingElement => "missing-element".to_string(),
            ErrorTag::BadElement => "bad-element".to_string(),
            ErrorTag::UnknownElement => "unknown-element".to_string(),
            ErrorTag::UnknownNamespace => "unknown-namespace".to_string(),
            ErrorTag::AccessDenied => "access-denied".to_string(),
            ErrorTag::LockDenied => "lock-denied".to_string(),
            ErrorTag::ResourceDenied => "resource-denied".to_string(),
            ErrorTag::RollbackFailed => "rollback-failed".to_string(),
            ErrorTag::DataExists => "data-exists".to_string(),
            ErrorTag::DataMissing => "data-missing".to_string(),
            ErrorTag::OperationNotSupported => "operation-not-supported".to_string(),
            ErrorTag::OperationFailed => "operation-failed".to_string(),
            ErrorTag::PartialOperation => "partial-pperation".to_string(),
            ErrorTag::MalformedMessage => "malformed-message".to_string(),
        }
    }
}

#[derive(Debug, Clone, Default)]
pub enum ErrorSeverity {
    #[default]
    Error,
    Warning,
}

impl ErrorSeverity {
    pub fn from_str(str: &str) -> Self {
        match str {
            "warning" => ErrorSeverity::Warning,
            _ => ErrorSeverity::Error,
        }
    }
}

impl EncodeString for ErrorSeverity {
    fn encode_string(&self) -> String {
        match self {
            ErrorSeverity::Error => "error".to_string(),
            ErrorSeverity::Warning => "warning".to_string(),
        }
    }
}

#[derive(Debug, Clone, Default)]
pub struct ErrorInfo {
    content: String,
}

trait EncodeString {
    fn encode_string(&self) -> String;
}

#[derive(Debug, Clone)]
pub enum SourceType {
    Candidate,
    Running,
    Startup,
}

impl SourceType {
    pub fn from_str(value: &str) -> SourceType {
        match value {
            "startup" => Self::Startup,
            "candidate" => Self::Candidate,
            _ => Self::Running,
        }
    }
}

impl EncodeString for SourceType {
    fn encode_string(&self) -> String {
        match self {
            Self::Candidate => "<candidate/>".to_string(),
            Self::Running => "<running/>".to_string(),
            Self::Startup => "<startup/>".to_string(),
        }
    }
}

#[derive(Debug, Clone, Default)]
pub enum OperationType {
    #[default]
    Merge,
    Replace,
    None,
}

impl EncodeString for OperationType {
    fn encode_string(&self) -> String {
        match self {
            OperationType::Merge => "merge".to_string(),
            OperationType::None => "none".to_string(),
            OperationType::Replace => "replace".to_string(),
        }
    }
}

#[derive(Debug, Clone, Default)]
pub enum ErrorOperationType {
    #[default]
    StopOnError,
    ContinueOnError,
    RollbackOnError,
}

impl EncodeString for ErrorOperationType {
    fn encode_string(&self) -> String {
        match self {
            ErrorOperationType::StopOnError => "stop-on-error".to_string(),
            ErrorOperationType::ContinueOnError => "continue-on-error".to_string(),
            ErrorOperationType::RollbackOnError => "rollback-on-error".to_string(),
        }
    }
}

#[derive(Debug, Clone, Default)]
pub enum TestOptionType {
    #[default]
    TestThenSet,
    Set,
    TestOnly,
}

impl EncodeString for TestOptionType {
    fn encode_string(&self) -> String {
        match self {
            TestOptionType::TestThenSet => "test-then-set".to_string(),
            TestOptionType::Set => "set".to_string(),
            TestOptionType::TestOnly => "test-only".to_string(),
        }
    }
}

pub trait NcMsgCodec {
    /// 解析报文消息
    fn decode(&mut self, data: &[u8]) -> (usize, Option<Vec<NcMsg>>);

    /// 封装报文消息
    fn encode(&mut self, msg: &NcMsg) -> Vec<u8>;
}

type LayerFlag = u32;
pub const LAYER_FLAG_DUMMY: LayerFlag = 0;
pub const LAYER_FLAG_CLOSE_SESSION: LayerFlag = 1;
pub const LAYER_FLAG_KILL_SESSION: LayerFlag = 2;

pub trait TreeDecoder {
    /// 从模式树中解析出实例
    fn decode_from(tree: &DataTree) -> Result<Self, Error>
    where
        Self: Sized;
}

pub fn error_operation() -> NcopMsg {
    NcopMsg::RpcError(
        crate::ErrorType::Application,
        crate::ErrorTag::OperationFailed,
        crate::ErrorSeverity::Error,
        crate::ErrorInfo {
            content: "not support agent".to_string(),
        },
    )
}

/// 按照查询的path进行匹配模型
pub fn list_modules_by_filter_paths(
    ctx: Arc<Context>,
    paths: &Vec<(String, String)>,
) -> Vec<(String, String)> {
    let mut modules = vec![];
    for module in ctx.modules(true) {
        for (_path, ns) in paths {
            if ns.eq(module.namespace()) {
                modules.push((module.namespace().to_string(), module.name().to_string()));
                break;
            }
        }
    }
    modules
}

pub fn error_with_msg(reason: String) -> NcopMsg {
    NcopMsg::RpcError(
        ErrorType::Application,
        ErrorTag::BadAttribute,
        ErrorSeverity::Error,
        ErrorInfo { content: reason },
    )
}

pub fn get_tree_data_with_paths(
    tree: RwLockReadGuard<'_, DataTree>,
    ctx: Arc<Context>,
    filter: &Vec<(String, String)>,
) -> DataTree {
    let mut data_tree = DataTree::new(&ctx);
    // 优先支持subtree
    if filter.is_empty() {
        return data_tree;
    }
    for (path, ns) in filter {
        match tree.find_xpath(path) {
            Ok(nodes_ref) => {
                for node_ref in nodes_ref {
                    if node_ref.schema().module().namespace().eq(ns) || ns.eq("") {
                        write_node_to_tree(&mut data_tree, &node_ref);
                    }
                }
            }
            Err(e) => {
                error!("find path from datatree failed, {}", e.to_string());
            }
        }
    }
    data_tree
}

pub fn write_node_to_tree(tree: &mut DataTree, node_ref: &DataNodeRef) {
    match node_ref.schema().kind() {
        SchemaNodeKind::List => {
            for ele in node_ref.children() {
                write_node_to_tree(tree, &ele);
            }
        }
        SchemaNodeKind::Container => {
            for ele in node_ref.children() {
                write_node_to_tree(tree, &ele);
            }
        }
        SchemaNodeKind::LeafList => {
            if let Some(value) = node_ref.value_canonical() {
                tree.new_path(&node_ref.path(), Some(&value), true).unwrap();
            }
        }
        SchemaNodeKind::Leaf => {
            if let Some(value) = node_ref.value_canonical() {
                tree.new_path(&node_ref.path(), Some(&value), true).unwrap();
            }
        }
        _ => (),
    }
}

/// 生成RPC回调索引
pub fn rpc_call_key(ns: &str, rpc_name: &str) -> String {
    format!("__rpc_{}_{}", ns, rpc_name)
}

/// 生成启动回调索引
pub fn init_call_key(ns: &str) -> String {
    format!("__init_{}", ns)
}

/// 生成配置回调索引
pub fn cfg_call_key(ns: &str) -> String {
    format!("__cfg_{}", ns)
}
